package com.tanxi521.tnlevel.utils;

import com.tanxi521.tnlevel.TnLevel;
import com.tanxi521.tnlevel.dao.SQLManager;
import com.tanxi521.tnlevel.listener.TnLevelListener;
import com.tanxi521.tnlevel.pojo.PlayerLevel;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Sound;
import org.bukkit.entity.Player;

import java.util.*;
import java.util.regex.Pattern;


/**
 * {@code @Author:} i-xiaotian
 * {@code @Date:} 2022/3/13 6:43
 * {@code @EMAIL:} 1254806967@qq.com
 */
public final class LevelCounter {

    private final static Map<String, PlayerLevel> PLAYER_LEVEL_CACHE_MAP = new HashMap<>();

    public static void putCache(String uuid, PlayerLevel playerLevel) {
        PLAYER_LEVEL_CACHE_MAP.put(uuid, playerLevel);
    }

    public static PlayerLevel getCache(String uuid) {
        PlayerLevel playerLevel = PLAYER_LEVEL_CACHE_MAP.get(uuid);
        if (Objects.isNull(playerLevel)) {
            playerLevel = SQLManager.gerPlayerLevelByUuid(uuid);
            putCache(uuid, playerLevel);
            return playerLevel;
        }
        return PLAYER_LEVEL_CACHE_MAP.get(uuid);
    }

    public static void removeCache(String uuid) {
        PLAYER_LEVEL_CACHE_MAP.remove(uuid);
    }

    public static void saveAll() {
        List<PlayerLevel> playerLevels = new ArrayList<>(PLAYER_LEVEL_CACHE_MAP.values());
        SQLManager.batchUpdatePlayer(playerLevels);
    }

    public static void initCounter() {
        TnLevelListener.totalExpList.clear();
        TnLevelListener.levels = ConfigReader.getLevels();

        long totalExp = 0L;
        List<Integer> levels = new ArrayList<>(TnLevelListener.levels.keySet());
        List<Long> exps = new ArrayList<>(TnLevelListener.levels.values());

        for (int i = 0; i < levels.size() - 1; i++) {
            totalExp += (levels.get(i + 1) - levels.get(i)) * exps.get(i);
            TnLevelListener.totalExpList.add(new HashMap<>(Collections.singletonMap(levels.get(i), totalExp)));
        }

        int lastIndex = levels.size() - 1;
        totalExp += (levels.get(lastIndex) - levels.get(lastIndex - 1)) * exps.get(lastIndex - 1);
        TnLevelListener.totalExpList.add(new HashMap<>(Collections.singletonMap(levels.get(lastIndex), totalExp)));
    }

    public static void countPlayerLevelByExp(Player p, int exp) {
        PlayerLevel playerLevel = getCache(p.getUniqueId().toString());
        int playerLevelOld = playerLevel.getPlayerLevel();
        long playerTotalExp = playerLevel.getPlayerTotalExp() + exp;

        Optional<HashMap<Integer, Long>> currentLevelExp = TnLevelListener.totalExpList.stream()
                .filter(map -> map.values().iterator().next() > playerTotalExp)
                .findFirst();

        int listIndex = TnLevelListener.totalExpList.indexOf(currentLevelExp.orElse(
                TnLevelListener.totalExpList.get(TnLevelListener.totalExpList.size() - 1)));

        int level;
        long cutExp = 0L;
        if (listIndex > 0) {
            cutExp = TnLevelListener.totalExpList.get(listIndex - 1).values().iterator().next();
        }

        level = TnLevelListener.totalExpList.get(listIndex).keySet().iterator().next();
        long perExpToNextLevel = TnLevelListener.levels.get(level);
        level += (int) ((playerTotalExp - cutExp) / perExpToNextLevel);
        long lastExp = (playerTotalExp - cutExp) % perExpToNextLevel;

        p.setLevel(level);
        p.setExp((float) lastExp / perExpToNextLevel);
        playerLevel.setPlayerLevel(level)
                .setPlayerExpToNextLevel(perExpToNextLevel)
                .setPlayerExp(lastExp)
                .setPlayerTotalExp(playerTotalExp);

        putCache(p.getUniqueId().toString(), playerLevel);

        if (level > playerLevelOld) {
            p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1F, 1F);
            String[] split = ConfigReader.getLevelUpTitle().split("-");
            String title = PlaceholderAPI.setPlaceholders(p, split[0].replace("&", "§"), Pattern.compile("%([^%]+)%"));
            String subTitle = PlaceholderAPI.setPlaceholders(p, split[1].replace("&", "§"), Pattern.compile("%([^%]+)%"));
            p.sendTitle(title, subTitle, 10, 70, 20);
        }
    }

    public static void countPlayerLevelByLevel(String playerName, int level, Player enchanter) {
        List<Integer> levelKeys = new ArrayList<>(TnLevelListener.levels.keySet());
        int listIndex = 0;
        boolean isFirst = false;
        boolean isSecond = false;

        for (int i = 0; i < levelKeys.size(); i++) {
            if (levelKeys.get(i) > level) {
                if (i == 1) isFirst = true;
                else if (i == 2) isSecond = true;
                else listIndex = i - 2;
                break;
            }
            if (i == levelKeys.size() - 1) {
                listIndex = i - 1;
                break;
            }
        }

        long baseExp = 0L;
        long perLevelNeedToUp;
        int preLevel;

        if (isFirst) {
            perLevelNeedToUp = TnLevelListener.levels.get(levelKeys.get(0));
            preLevel = levelKeys.get(0);
        } else if (isSecond) {
            baseExp = TnLevelListener.totalExpList.get(0).values().iterator().next();
            perLevelNeedToUp = TnLevelListener.levels.get(levelKeys.get(1));
            preLevel = levelKeys.get(1);
        } else {
            baseExp = TnLevelListener.totalExpList.get(listIndex).values().iterator().next();
            perLevelNeedToUp = TnLevelListener.levels.get(levelKeys.get(listIndex + 1));
            preLevel = levelKeys.get(listIndex + 1);
        }

        long playerTotalExp = baseExp + (level - preLevel) * perLevelNeedToUp;
        PlayerLevel playerLevel = new PlayerLevel();

        enchanter.setLevel(level);
        enchanter.setExp(0);
        enchanter.playSound(enchanter.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1F, 1F);

        playerLevel.setDisplayName(playerName)
                .setPlayerLevel(level)
                .setPlayerExp(0L)
                .setPlayerExpToNextLevel(perLevelNeedToUp)
                .setPlayerTotalExp(playerTotalExp);

        putCache(enchanter.getUniqueId().toString(), playerLevel);
    }

    public static void updatePlayerData(PlayerLevel playerLevel) {
        SQLManager.UpdatePlayer(playerLevel);
    }
}
